About
Giant Spectrum is an interactive audio-visual wall piece that displays a live, moving spectral representation of the sounds it hears. Much like light, sound is comprised of many different frequencies, and different sounds contain changing frequencies with varying amplitudes. As sounds occur, the piece shows these changing parts in real time as a moving spectral display. Any sound present in the room appears on the display.

The piece is made of aluminum, polycarbonate, and custom electronics. It was constructed over a four month period from December 2015 to March 2016.

Giant Spectrum was exhibited in "Sweet Gongs Vibrating" at the San Diego Art Institute, and in the WaveCave gallery at California Institute of the Arts during 2016.

San Diego City Beat had this to say in an article featuring Giant Spectrum:

The standout of the show, however, has to be Cooper Baker's "Giant Spectrum," a nearly four-foot aluminum and plastic light sculpture that responds to the noise of the room. Stand next to it silently and it will pick up the viewer's slightest movements. Make a bunch of noise and the plastic, vacuformed lights start to sporadically flash. (... full text)

SDAI Photos
CalArts Photos
Video
testing the frequency and amplitude response
Build Photos
Circuitry
Source Code
//------------------------------------------------------------------------------
//  Spectral display with MSGEQ7 and shift registers for Arduino
//
//  spectrum.ino
//
//  Created by Cooper Baker on 12/13/15.
//  Copyright (c) 2016 Cooper Baker. All rights reserved.
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
// includes
//------------------------------------------------------------------------------

// Include xcode.h for syntax coloring
#include "xcode.h"
#include "EEPROM.h"
#include <math.h>


//------------------------------------------------------------------------------
// definitions
//------------------------------------------------------------------------------

// shift register pins
#define SCLK 12
#define RCLK 11
#define SOUT 10
#define OE   9

// msgeq7 pins
#define STROBE  8
#define RESET   7
#define EQ_DATA 0

// switch pin
#define MODE 4

// switch debounce time
#define DEBOUNCE_MSEC 40

// led refresh rate
#define REFRESH_MSEC 20

// range averaging coefficients
#define MAX_AVG_COEFF 0.9991
#define MIN_AVG_COEFF 0.99999

// threshold coefficients
#define MAX_THRESH 0.666
#define MIN_THRESH 0.05

// meter decay decrements
#define DECAY_FAST  0.026
#define DECAY_SLOW  0.005

// pwm values for brightness
#define BRIGHT 0
#define DIM    192

// eeprom address
#define EEPROM_MODE_ADDR 0

// clipping macro
#define clip( x, l, h ) (x) < (l) ? (l) : ( (x) > (h) ? (h) : (x) )


//------------------------------------------------------------------------------
// prototypes
//------------------------------------------------------------------------------
void        setup               ( void );
void        loop                ( void );
void        signature           ( void );
void        upDown              ( void );
inline void updateLeds          ( void );
inline void readEq              ( void );
inline void calculateValues     ( void );
inline void setMode             ( byte index );
inline void allOff              ( void );


//------------------------------------------------------------------------------
// variables
//------------------------------------------------------------------------------

// signature array
byte sig[] =
{
    B00000000, B00000000, B00000000, B00000000, // space
    B00000000, B00000000, B00000000, B00000000, // space
    B00111110, B00100010, B00100010, B00000000, // C
    B00111110, B00100010, B00111110, B00000000, // O
    B00111110, B00100010, B00111110, B00000000, // O
    B00111110, B00101000, B00111000, B00000000, // P
    B00111110, B00101010, B00101010, B00000000, // E
    B00111110, B00101000, B00110110, B00000000, // R
    B00000000, B00000000, B00000000, B00000000, // space
    B00111110, B00101010, B00110110, B00000000, // B
    B00111110, B00101000, B00111110, B00000000, // A
    B00111110, B00001000, B00110110, B00000000, // K
    B00111110, B00101010, B00101010, B00000000, // E
    B00111110, B00101000, B00110110, B00000000, // R
    B00000000, B00000000, B00000000, B00000000, // space
    B00101110, B00101010, B00111010, B00000000, // 2
    B00111110, B00100010, B00111110, B00000000, // O
    B00100010, B00111110, B00000010, B00000000, // 1
    B00111110, B00101010, B00101110, B00000000, // 6
    B00000000, B00000000, B00000000, B00000000, // space
    B00000000, B00000000, B00000000, B00000000  // space
};

// numeral patterns
byte num[] =
{
    B00000000, B00000000, B00100010, B00111110, B00000010, B00000000, B00000000, // 1
    B00000000, B00000000, B00101110, B00101010, B00111010, B00000000, B00000000, // 2
    B00000000, B00000000, B00101010, B00101010, B00111110, B00000000, B00000000, // 3
    B00000000, B00000000, B00111000, B00111110, B00001000, B00000000, B00000000  // 4
};

// bit patterns for led 'dots'
// dot[ 0 ] = all off, dot[ 8 ] = max
byte dot[ 9 ] = { B00000000,
                  B00000001,
                  B00000010,
                  B00000100,
                  B00001000,
                  B00010000,
                  B00100000,
                  B01000000,
                  B10000000 };

// values from adc
float raw[ 7 ] = { 0, 0, 0, 0, 0, 0, 0 };

// spectrum values between 0 and 1
float spec[ 7 ] = { 0, 0, 0, 0, 0, 0, 0 };

// remapped bands of spectrum values
float band[ 7 ]  = { 0, 0, 0, 0, 0, 0, 0 };

// max / min averages per band
float max[ 7 ] = { 1, 1, 1, 1, 1, 1, 1 };
float min[ 7 ] = { 0, 0, 0, 0, 0, 0, 0 };

// max / min peak detection
float maxPeak[ 7 ][ 3 ] = { { 0 } };
float minPeak[ 7 ][ 3 ] = { { 0 } };

// bytes for shift registers
byte out[ 7 ] = { 0, 0, 0, 0, 0, 0, 0 };

// intermediate averaging coefficients
float maxInCoeff;
float minInCoeff;

// transfer funciton array
float xfer[ 100 ] = { 0 };

// mode switch variables
byte mode;
byte swMode;
byte swModePrev;
byte swModeState;

// timer variables
unsigned long msec;
unsigned long nextMsec;
unsigned long swModeMsec;

// decay decrement
float decayDec;


//------------------------------------------------------------------------------
// signature - scrolls sig[] array across the display
//------------------------------------------------------------------------------
void signature( void )
{
    byte i;
   
    // scroll through signature array
    for( i = 0 ; i < 77 ; i++ )
    {
        // disable latch
        digitalWrite( RCLK, LOW );
       
        // move bytes to shift registers
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 6 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 5 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 4 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 3 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 2 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 1 ] );
        shiftOut( SOUT, SCLK, MSBFIRST, sig[ i + 0 ] );
       
        // enable latch
        digitalWrite( RCLK, HIGH );

        // wait
        delay( 275 );
    }
}


//------------------------------------------------------------------------------
// upDown - moves a horizontal bar up and down the display
//------------------------------------------------------------------------------
void upDown( void )
{
    byte i;
    byte j;
   
    for( i = 1 ; i < 8 ; i++ )
    {
        for( j = 0 ; j < 7 ; j++ )
        {
            out[ j ] = dot[ i ];
        }
       
        updateLeds();
       
        delay( 152);
    }

    for( i = 6 ; i > 0 ; i-- )
    {
        for( j = 0 ; j < 7 ; j++)
        {
            out[ j ] = dot[ i ];
        }
       
        updateLeds();
       
        delay( 152 );
    }
}


//------------------------------------------------------------------------------
// updateLeds - shifts bits from out[] array to led shift registers
//------------------------------------------------------------------------------
inline void updateLeds( void )
{
    // disable latch
    digitalWrite( RCLK, LOW );

    // move bytes to shift registers
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 6 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 5 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 4 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 3 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 2 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 1 ] );
    shiftOut( SOUT, SCLK, MSBFIRST, out[ 0 ] );

    // enable latch
    digitalWrite( RCLK, HIGH );
}


//------------------------------------------------------------------------------
// readEq - reads analog values from the msgeq7 chip
//------------------------------------------------------------------------------
inline void readEq( void )
{
    byte i;
   
    // reset msgeq7 multiplexor
    digitalWrite     ( RESET,  HIGH );
    delayMicroseconds( 10 );
    digitalWrite     ( RESET,  LOW  );
    digitalWrite     ( STROBE, HIGH );
    delayMicroseconds( 80 );
   
    // read the eq values
    for( i = 0 ; i < 7 ; i++ )
    {
        digitalWrite     ( STROBE, LOW  );
        delayMicroseconds( 40  );

        // lowpass the raw values
        raw[ i ] = analogRead( EQ_DATA ) * 0.9 + raw[ i ] * 0.1;
   
        digitalWrite     ( STROBE, HIGH );
        delayMicroseconds( 40 );
    }
}


//------------------------------------------------------------------------------
// calculateValues - calculates spectrum values using data in raw[] array
//------------------------------------------------------------------------------
inline void calculateValues( void )
{
    int   i;
    float x;

    float minIn;
    float maxIn;
   
    // iterate through the raw bands
    for( i = 0 ; i < 7 ; i++ )
    {
        // scale raw band value between 0 and 1
        x = raw[ i ] / 1024.0;

        // minimum averaging
        //----------------------------------------------------------------------

        // look at last three values
        minPeak[ i ][ 2 ] = minPeak[ i ][ 1 ];
        minPeak[ i ][ 1 ] = minPeak[ i ][ 0 ];
        minPeak[ i ][ 0 ] = x;
       
        // find valleys
        if( ( minPeak[ i ][ 0 ] > minPeak[ i ][ 1 ] ) && ( minPeak[ i ][ 1 ] < minPeak[ i ][ 2 ] ) )
        {
            minIn = minPeak[ i ][ 1 ];
        }
        else
        {
            minIn = min[ i ];
        }
       
        // calculate weighted min average ( lowpass filter )
        min[ i ] = min[ i ] * MIN_AVG_COEFF + minIn * minInCoeff;

        // discard values below min
        x -= min[ i ];
       
        // normalize
        x *= 1.0 / ( 1.0 - min[ i ] );

        // clip between 0 and 1
        x = clip( x, 0, 1 );
       
       
        // maximum averaging
        //----------------------------------------------------------------------

        // look at last three values
        maxPeak[ i ][ 2 ] = maxPeak[ i ][ 1 ];
        maxPeak[ i ][ 1 ] = maxPeak[ i ][ 0 ];
        maxPeak[ i ][ 0 ] = x;
       
        // find peaks
        if( ( maxPeak[ i ][ 0 ] < maxPeak[ i ][ 1 ] ) && ( maxPeak[ i ][ 1 ] > maxPeak[ i ][ 2 ] ) )
        {
            maxIn = maxPeak[ i ][ 1 ];
        }
        else
        {
            maxIn = max[ i ];
        }

        // update max peak average
        max[ i ] = maxIn > max[ i ] ? maxIn : max[ i ];

        // calculate weighted max average ( lowpass filter )
        max[ i ] = max[ i ] * MAX_AVG_COEFF + maxIn * maxInCoeff;

        // set minimum max average to MAX_THRESH
        max[ i ] = max[ i ] < MAX_THRESH ? MAX_THRESH : max[ i ];
       
        // normalize to average range
        x /= max[ i ] - min[ i ];


        // scaling
        //----------------------------------------------------------------------

        // drop values below threshold
        x -= MIN_THRESH;
        x *= 1.0 / ( 1.0 - MIN_THRESH );
       
        // scale a little larger
        x *= 1.2;

        x -= MIN_THRESH;

        // constrain between 0 and 1
        x = clip( x, 0, 1 );

        // output transfer function
        x = xfer[ int( x * 99 ) ];

        // update value for display
        spec[ i ] = x > spec[ i ] ? x : spec[ i ];
    }

    // remap middle 5 input bands onto 7 output bands
    band[ 0 ] = spec[ 1 ] * 0.950000 + spec[ 2 ] * 0.050000;
    band[ 1 ] = spec[ 1 ] * 0.285714 + spec[ 2 ] * 0.714286;
    band[ 2 ] = spec[ 2 ] * 0.714286 + spec[ 3 ] * 0.285714;
    band[ 3 ] = spec[ 2 ] * 0.250000 + spec[ 3 ] * 0.500000 + spec[ 4 ] * 0.25;
    band[ 4 ] = spec[ 3 ] * 0.285714 + spec[ 4 ] * 0.714286;
    band[ 5 ] = spec[ 4 ] * 0.714286 + spec[ 5 ] * 0.285714;
    band[ 6 ] = spec[ 5 ] * 0.900000 + spec[ 4 ] * 0.100000;

    // create output values for display
    for( i = 0 ; i < 7 ; i++ )
    {
        // constrain between 0 and 1
        x = clip( band[ i ], 0, 1 );
       
        // use remapped values for display
        out[ i ] = dot[ ( int )( x * 8.0 ) ];

        // update decay filter
        spec[ i ] -= decayDec;
    }
}


//------------------------------------------------------------------------------
// setMode - sets display mode based on index ( 1 - 4 )
//------------------------------------------------------------------------------
inline void setMode( byte index )
{
    switch( mode )
    {
        // fast, bright
        case 1 :    decayDec = DECAY_FAST;
                    analogWrite( OE, BRIGHT );
                    break;
           
        // fast, dim
        case 2 :    decayDec = DECAY_FAST;
                    analogWrite( OE, DIM );
                    break;

        // slow, bright
        case 3 :    decayDec = DECAY_SLOW;
                    analogWrite( OE, BRIGHT );
                    break;
           
        // slow, dim
        case 4 :    decayDec = DECAY_SLOW;
                    analogWrite( OE, DIM );
                    break;
    }
}


//------------------------------------------------------------------------------
// numeral - displays a numeral based on value ( 1 - 4 )
//------------------------------------------------------------------------------
inline void numeral( byte value )
{
    allOff();
   
    value = value - 1;
   
    out[ 6 ] = num[ 6 + value * 7 ];
    out[ 5 ] = num[ 5 + value * 7 ];
    out[ 4 ] = num[ 4 + value * 7 ];
    out[ 3 ] = num[ 3 + value * 7 ];
    out[ 2 ] = num[ 2 + value * 7 ];
    out[ 1 ] = num[ 1 + value * 7 ];
    out[ 0 ] = num[ 0 + value * 7 ];
   
    updateLeds();
    delay( 500 );
   
    allOff();
}


//------------------------------------------------------------------------------
// allOff - turns off all leds
//------------------------------------------------------------------------------
inline void allOff( void )
{
    byte i;
   
    for( i = 0 ; i < 7 ; i++ )
    {
        out [ i ] = 0;
    }
   
    updateLeds();
}


//------------------------------------------------------------------------------
// setup - initializes arduino board
//------------------------------------------------------------------------------
void setup( void )
{
    // disable leds
    pinMode( OE, OUTPUT );
    analogWrite( OE, 255 );

    // configure pins
    pinMode( SCLK,   OUTPUT );
    pinMode( RCLK,   OUTPUT );
    pinMode( SOUT,   OUTPUT );
    pinMode( OE,     OUTPUT );
    pinMode( STROBE, OUTPUT );
    pinMode( RESET,  OUTPUT );
    pinMode( EQ_DATA, INPUT );
    pinMode( MODE,    INPUT );
 
    // configure 5v analog reference for msgeq7
    analogReference( DEFAULT );
   
    // initialize variables
    msec        = 0;
    nextMsec    = 0;
    swMode      = 0;
    swModePrev  = 0;
    swModeMsec  = 0;
    swModeState = 0;
    maxInCoeff  = 1.0 - MAX_AVG_COEFF;
    minInCoeff  = 1.0 - MIN_AVG_COEFF;
   
    // read mode from memory
    mode = EEPROM.read( EEPROM_MODE_ADDR );

    // calculate transfer function
    float x;
    for( byte i = 0 ; i < 100 ; i++ )
    {
        x = float( i ) / 100.0;
        xfer[ i ] = cos( 3.141592 * pow( x, 0.5 ) ) * -0.5 + 0.5;
    }
   
    // set mode
    setMode( mode );

    // initial display
    allOff();
    upDown();
    signature();
}


//------------------------------------------------------------------------------
// loop - the main program loop
//------------------------------------------------------------------------------
void loop( void )
{
    // read values from msgeq7
    readEq();

    // timer
    //--------------------------------------------------------------------------

    // store current millisecond value
    msec = millis();


    // spectrum
    //--------------------------------------------------------------------------

    // refresh spectrum if enough milliseconds have elapsed
    if( ( long )( msec - nextMsec ) >= 0 )
    {
        // calculate next update time
        nextMsec = msec + REFRESH_MSEC;

        // update the spectrum
        calculateValues();
        updateLeds();
    }


    // switch
    //--------------------------------------------------------------------------
   
    // store switch state
    swMode = digitalRead( MODE );
   
    // if switch state has changed
    if( swMode != swModePrev )
    {
        // store current millisecond value
        swModeMsec = millis();
    }

    // if the debounce interval has elapsed
    if( ( millis() - swModeMsec ) > DEBOUNCE_MSEC )
    {
        // if stitch state has changed
        if( swMode != swModeState )
        {
            // store switch state
            swModeState = swMode;
           
            // if switch is on
            if( swModeState == HIGH )
            {
                // increment and wrap mode value
                mode++;
                mode > 4 ? mode = 1 : mode;
               
                // set the mode
                setMode( mode );

                // display the mode
                numeral( mode );
 
                // save mode value to memory
                EEPROM.write( EEPROM_MODE_ADDR, mode );
            }
        }
    }

    // store swtich state
    swModePrev = swMode;
}


//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------